using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using UltraLight.MVVM.Contracts;

namespace UltraLight.MVVM
{
    /// <summary>
    ///     Global MVVM Manager
    /// </summary>
    public static class UltraLightLocator
    {
        /// <summary>
        ///     Hold View model references
        /// </summary>
        private static readonly Dictionary<Type, IViewModel> _viewModels = new Dictionary<Type,IViewModel>();

        private static IEventAggregator _eventAggregator;

        public static IEventAggregator EventAggregator
        {
            get { return _eventAggregator ?? (_eventAggregator = new EventAggregatorService()); }
        }

        /// <summary>
        ///     When a page is tombstoned
        /// </summary>
        /// <param name="phonePage">The page</param>
        public static void DeactivatePage<T>(this PhoneApplicationPage phonePage) where T: IViewModel
        {
            var viewModel = GetViewModel<T>();
            if (viewModel is ITombstoneFriendly)
            {
                ((ITombstoneFriendly)viewModel).Deactivate();
            }
        }

        /// <summary>
        ///     Activate page
        /// </summary>
        /// <remarks>
        ///     We allow a control, not a page here, in case you have a Panorama or Pivot control,
        ///     you'll want to hook to that loaded event instead of the page so it initializes
        ///     correctly
        /// </remarks>
        /// <param name="element">The root element - page, Grid, Pivot, etc.</param>
        public static void ActivatePage<T>(this FrameworkElement element) where T: IViewModel
        {
            var viewModel = GetViewModel<T>();
            RoutedEventHandler loaded = null;
            loaded = (o, e) =>
            {
// ReSharper disable AccessToModifiedClosure
                ((FrameworkElement)o).Loaded -= loaded;
// ReSharper restore AccessToModifiedClosure
                if (viewModel is ITombstoneFriendly)
                {
                    // we queue this on the dispatcher even if we know we're already on the UI thread
                    // this allows controls like Pivot to finish their initialization before we activate 
                    // the view model bindings
                    Deployment.Current.Dispatcher.BeginInvoke(() => ((ITombstoneFriendly) viewModel).Activate());                    
                }                
            };
            element.Loaded += loaded;
        }        

        /// <summary>
        ///     Bind a page to a view model
        /// </summary>
        /// <param name="phonePage">The page</param>
        /// <param name="root">The root element</param>
        public static void BindToViewModel<T>(this PhoneApplicationPage phonePage, FrameworkElement root) where T: IViewModel 
        {
            if (DesignerProperties.IsInDesignTool) return;

            RoutedEventHandler loaded = null;

            var vm = GetViewModel<T>(); // access to modified closure

            loaded = (o, e) =>
                            {
                                var page = o as PhoneApplicationPage;
                                if (page == null)
                                {
                                    return;
                                }

                                // unhook loaded so we do this only once
// ReSharper disable AccessToModifiedClosure
                                page.Loaded -= loaded;
// ReSharper restore AccessToModifiedClosure

                                // provide an implementation for the dialog
                                vm.Dialog = new Dialog();

                                vm.GoToVisualState = (state, useTransitions) =>
                                                     ExecuteOnUI(
                                                         () =>
                                                         VisualStateManager.GoToState(phonePage, state, useTransitions));


                                // provide an implementation for navigation
                                vm.RequestNavigation =
                                    newPage => page.NavigationService.Navigate(new Uri(newPage, UriKind.Relative));

                                vm.GoBack = page.NavigationService.GoBack;

                                page.BackKeyPress += (sender, args) =>
                                                         {
                                                             args.Cancel = vm.CancelBackRequest();
                                                         };

                                // bind the view model
                                root.DataContext = vm;

                                // bind the application bar buttons and menu items
                                var idx = 0;

                                if (page.ApplicationBar == null) return;

                                if ((page.ApplicationBar.Buttons != null) && (vm.ApplicationButtonBindings != null))
                                {
                                    foreach (var command in vm.ApplicationButtonBindings)
                                    {
                                        if (page.ApplicationBar.Buttons.Count <= idx) break;

                                        var appButton = page.ApplicationBar.Buttons[idx++] as ApplicationBarIconButton;

                                        if (appButton == null) continue;
                                        var command1 = command;
                                        command.CanExecuteChanged += (cmd, arg) =>
                                                                     appButton.IsEnabled =
                                                                     command1.CanExecute(null);
                                        appButton.Click += (cmd, arg) =>
                                                           command1.Execute(null);
                                    }
                                }
                                idx = 0;
                                if ((page.ApplicationBar.MenuItems != null) && (vm.ApplicationMenuBindings != null)) {

                                  foreach (var command in vm.ApplicationMenuBindings) 
                                  {
                                    if (page.ApplicationBar.MenuItems.Count <= idx) break;

                                    var appMenu = page.ApplicationBar.MenuItems[idx++] as ApplicationBarMenuItem;

                                    if (appMenu == null) continue;
                                    var command1 = command;
                                    command.CanExecuteChanged += (cmd, arg) =>
                                                                 appMenu.IsEnabled =
                                                                 command1.CanExecute(null);
                                    appMenu.Click += (cmd, arg) =>
                                                     command1.Execute(null);
                                  }
                                }
                            };

            phonePage.Loaded += loaded;
        }
        
        /// <summary>
        ///     Bind a user control to a view model
        /// </summary>
        /// <param name="userControl">The user control</param>
        /// <param name="root">The root layout</param>
        public static void BindToViewModel<T>(this UserControl userControl, FrameworkElement root) where T: IViewModel
        {
            if (DesignerProperties.IsInDesignTool) return;

            RoutedEventHandler loaded = null;

            var vm = GetViewModel<T>(); // access to modified closure

            loaded = (o, e) =>
                         {
                             var control = o as UserControl;
                             if (control == null)
                             {
                                 return;
                             }

                             // unhook loaded so we do this only once
// ReSharper disable AccessToModifiedClosure
                             control.Loaded -= loaded;
// ReSharper restore AccessToModifiedClosure

                             // provide an implementation for the dialog
                             vm.Dialog = new Dialog();

                             vm.GoToVisualState = (state, useTransitions) =>
                                                     ExecuteOnUI(
                                                         () =>
                                                         VisualStateManager.GoToState(userControl, state, useTransitions));

                            // hook to the navigation of the parent
                            var parent = VisualTreeHelper.GetParent(control);
                            while (parent != null)
                            {
                                if (parent is PhoneApplicationPage)
                                {
                                    vm.RequestNavigation =
                                        newPage => ((PhoneApplicationPage)parent).NavigationService.Navigate(new Uri(newPage, UriKind.Relative));
                                    vm.GoBack = ((PhoneApplicationPage) parent).NavigationService.GoBack;
                                    ((PhoneApplicationPage)parent).BackKeyPress += (sender, args) =>
                                    {
                                        args.Cancel = vm.CancelBackRequest();
                                    };

                                    break;
                                }
                                parent = VisualTreeHelper.GetParent(parent);
                            }

                             // bind the view model
                             root.DataContext = vm;                    
                         };

            userControl.Loaded += loaded;
        }

        /// <summary>
        ///     Register a view model by type
        /// </summary>
        /// <typeparam name="T">The type, preferably an interface for the view model</typeparam>
        /// <param name="viewModel">The instance to register</param>
        public static void Register<T>(T viewModel) where T: IViewModel
        {
            if(!_viewModels.ContainsKey(typeof (T)))
                _viewModels.Add(typeof(T), viewModel);
        }

        /// <summary>
        ///     Get the view model for the type
        /// </summary>
        /// <typeparam name="T">The type, preferably an interface for the view model</typeparam>
        /// <returns>The instance of the view model previously registered</returns>
        public static T GetViewModel<T>() where T: IViewModel
        {
            return _viewModels.ContainsKey(typeof (T)) ? (T) _viewModels[typeof (T)] : default(T);
        }

        private static readonly Dispatcher _dispatcher = Deployment.Current.Dispatcher; 

        /// <summary>
        ///     Execute on the UI thread
        /// </summary>
        /// <param name="action">Action to execute</param>
        public static void ExecuteOnUI(Action action)
        {
            if (_dispatcher.CheckAccess())
            {
                action();
            }
            else
            {
                _dispatcher.BeginInvoke(action);
            }
        }
    }
}